{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "059756b7",
   "metadata": {},
   "source": [
    "# LangGraph ToolNode\n",
    "\n",
    "- Author: [JoonHo Kim](https://github.com/jhboyo)\n",
    "- Peer Review :\n",
    "- Proofread : [Chaeyoon Kim](https://github.com/chaeyoonyunakim)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/01-Core-Features/10-LangGraph-ToolNode.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/01-Core-Features/10-LangGraph-ToolNode.ipynb)\n",
    "\n",
    "## Overview\n",
    "In this tutorial, we will cover how to use ```LangGraph```'s pre-built ```ToolNode``` for tool invocation.\n",
    "```ToolNode``` is a ```LangChain Runnable``` that takes a graph state containing a list of messages as input and updates the state with the result of the tool invocation.\n",
    "\n",
    "It is designed to work seamlessly with ```LangGraph```'s pre-built agents and can operate with any ```StateGraph```, provided the state includes a messages key with an appropriate reducer.\n",
    "\n",
    "\n",
    "Now, let’s explore how to maximize productivity using ```LangGraph ToolNode```. 🚀\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environment Setup](#environment-setup)\n",
    "- [Define tools](#define-tools)\n",
    "- [Manually call ToolNode](#manually-call-toolnode)\n",
    "- [Using with LLMs](#using-with-llms)\n",
    "- [Using with Agent](#using-with-agent)\n",
    "\n",
    "### References\n",
    "\n",
    "- [How to call tools using ToolNode](https://langchain-ai.github.io/langgraph/how-tos/tool-calling/)\n",
    "- [ToolNode](https://github.com/langchain-ai/langgraph/blob/main/libs/langgraph/langgraph/prebuilt/tool_node.py)\n",
    "- [Tool Calling](https://python.langchain.com/docs/concepts/tool_calling/)\n",
    "- [AIMessage](https://python.langchain.com/docs/concepts/messages/#aimessage)\n",
    "- [ChatOpenAI](https://python.langchain.com/api_reference/openai/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html)\n",
    "- [ChatModel](https://python.langchain.com/docs/integrations/chat/)\n",
    "- [ReAct Implementation](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/#react-implementation)\n",
    "- [How to handle tool calling errors](https://langchain-ai.github.io/langgraph/how-tos/tool-calling-errors/)\n",
    "----"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "923a83b4",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n",
    "\n",
    "**[Note]**\n",
    "- ```langchain-opentutorial``` is a package that provides a set of easy-to-use environment setup, useful functions and utilities for tutorials. \n",
    "- You can checkout the [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "8e8ad1ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install -qU langchain-opentutorial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "446f6eba",
   "metadata": {},
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "# Attempt to load environment variables from a .env file; if unsuccessful, set them manually.\n",
    "if not load_dotenv():\n",
    "    set_env(\n",
    "        {\n",
    "            \"OPENAI_API_KEY\": \"\",\n",
    "            \"LANGCHAIN_API_KEY\": \"\",\n",
    "            \"LANGCHAIN_TRACING_V2\": \"false\",\n",
    "            \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "            \"LANGCHAIN_PROJECT\": \"LangGraph-ToolNode\",\n",
    "        }\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "70361e4a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [\n",
    "        \"langchain\",\n",
    "        \"langchain_community\",\n",
    "        \"langchain_core\",\n",
    "        \"langchain_experimental\",\n",
    "        \"langchain_openai\",\n",
    "        \"feedparser\",\n",
    "        \"IPython\",\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f5877653",
   "metadata": {},
   "source": [
    "\n",
    "## Creating tools\n",
    "Before creating tools, we will build some functions to collect and fetch news from ```Google News``` for keyword that user input.\n",
    "\n",
    "You can refer to ```Creating Tools``` section in [Tool Calling Agent with More LLM Models](https://langchain-opentutorial.gitbook.io/langchain-opentutorial/15-agent/04-agent-more-llms) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "6cc115a7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List, Dict, Optional\n",
    "from urllib.parse import quote\n",
    "import feedparser\n",
    "\n",
    "\n",
    "class GoogleNews:\n",
    "\n",
    "    def _fetch_news(self, url: str, k: int = 3) -> List[Dict[str, str]]:\n",
    "        news_data = feedparser.parse(url)\n",
    "        return [\n",
    "            {\"title\": entry.title, \"link\": entry.link}\n",
    "            for entry in news_data.entries[:k]\n",
    "        ]\n",
    "\n",
    "    def _collect_news(self, news_list: List[Dict[str, str]]) -> List[Dict[str, str]]:\n",
    "        if not news_list:\n",
    "            print(\"There is no news for the keyword.\")\n",
    "            return []\n",
    "\n",
    "        result = []\n",
    "        for news in news_list:\n",
    "            result.append({\"url\": news[\"link\"], \"content\": news[\"title\"]})\n",
    "\n",
    "        return result\n",
    "\n",
    "    def search_by_keyword(\n",
    "        self, keyword: Optional[str] = None, k: int = 3\n",
    "    ) -> List[Dict[str, str]]:\n",
    "\n",
    "        if keyword:\n",
    "            encoded_keyword = quote(keyword)\n",
    "            url = f\"https://news.google.com/rss/search?q={encoded_keyword}&hl=en&gl=US&ceid=US:en\"\n",
    "        else:\n",
    "            url = f\"https://news.google.com/rss?hl=en&gl=US&ceid=US:en\"\n",
    "\n",
    "        news_list = self._fetch_news(url, k)\n",
    "        return self._collect_news(news_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e081f153",
   "metadata": {},
   "source": [
    "Now Let's create tools that search News for query and execute python code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9cc80ca5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.tools import tool\n",
    "from langchain_experimental.tools.python.tool import PythonAstREPLTool\n",
    "from typing import List, Dict\n",
    "\n",
    "@tool\n",
    "def search_news(query: str) -> List[Dict[str, str]]:\n",
    "    \"\"\"Search Google News by input keyword\"\"\"\n",
    "    news_tool = GoogleNews()\n",
    "    return news_tool.search_by_keyword(query, k=5)\n",
    "\n",
    "\n",
    "@tool\n",
    "def python_code_interpreter(code: str):\n",
    "    \"\"\"Call to execute python code.\"\"\"\n",
    "    return PythonAstREPLTool().invoke(code)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e4da837",
   "metadata": {},
   "source": [
    "Next, we will explore how to use ```ToolNode``` to invoke tools.\n",
    "\n",
    "```ToolNode``` is initialized with a list of tools and the following arguments.\n",
    "\n",
    "* ```Args:```\n",
    "    * ```tools```: A sequence of tools that can be invoked by the ```ToolNode```.\n",
    "    * ```name```: The name of the ```ToolNode``` in the graph. Defaults to \"tools\".\n",
    "    * ```tags```: Optional tags to associate with the node. Defaults to \"None\".\n",
    "    * ```handle_tool_errors```: How to handle tool errors raised by tools inside the node. Defaults to \"True\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "ec0b7d34",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.prebuilt import ToolNode, tools_condition\n",
    "\n",
    "# Generate tool list\n",
    "tools = [search_news, python_code_interpreter]\n",
    "\n",
    "# Initialize ToolNode\n",
    "tool_node = ToolNode(tools, handle_tool_errors=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9c4b22a",
   "metadata": {},
   "source": [
    "## Calling ToolNode manually"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f3e9011",
   "metadata": {},
   "source": [
    "```ToolNode``` operates on graph state with a list of messages.\n",
    "\n",
    "```AIMessage``` is used to represent a message with the role assistant. This is the response from the model, which can include text or a request to invoke tools. It could also include other media types like images, audio, or video though this is still uncommon at the moment.\n",
    "* ```Args:```\n",
    "    * ```content```: The content of the message. Usually a string, but can be a list of content blocks.\n",
    "    * ```tool_calls```: A list of tool calls associated with the message.\n",
    "        * ```name```: The name of the tool to invoke.\n",
    "        * ```args```: The arguments to pass to the tool.\n",
    "        * ```id```: An optional unique identifier for the message, ideally provided by the provider/model that created the message.\n",
    "        * ```type```: The type of the message.\n",
    "\n",
    "It expects the last message in the list to be an ```AIMessage``` with ```tool_calls``` parameter.\n",
    "\n",
    "Let’s see how to manually invoke ```ToolNode```."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "265b2b64",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [ToolMessage(content='[{\"url\": \"https://news.google.com/rss/articles/CBMingFBVV95cUxNSjY3UDQyQ0xaSlNUcDBQV0I3VnBJck9wSkRWdTJ4ZU0yUkQwQm9iVGNaYjRva3ZqTmR3ZHFySU9VQTM5OW04NTNCZm8yZU0tekFYSEhLSmFiNGQ0VlNna1VhQk1aS3Y0ZUc5QllXdjA1Z3VCTVBFUW9iUEJhMHBsWWthWGdiTUw4RnZlNENETXNWXzQ0MGFLUXJYMXpfZ9IBowFBVV95cUxNQXVlRG50cGJxUmZNSkFSbnBua1hkaFhZNi14V25PWkxJRDZPZkhxRDVYV3NnQm1xaWZFMEFMeHhESGtwWFBlOUpueGlTa0NKUlNDV2R3N3VfTi1wWmFNdVhGNE85czd5aU9oOGpmc2xkSWM1Q1JZWXgycXZ1X3lPd0ROTHVEOEVOSWxfMzlLTnltaG1ESjdhMWZvZFYyWC10Y1lR?oc=5\", \"content\": \"Tesla starts sales of revamped Model Y in U.S. for about $60,000 - CNBC\"}, {\"url\": \"https://news.google.com/rss/articles/CBMijAFBVV95cUxQUDhkX2tIUmRNT1J2VExLYmpsYzNSMlB0cTFIcmg5aUtiVEE2cnhfb1lWOGRCTVR4bjYyMk1TMk0tUDc4d3Y5Y1BKd2o0TjZaWnlEa3JKbjY0djh3M25DX0U5dlFGM0RUcFVidllYaDdFcUFGNjBuYkdtWGM4VlRGQi05TElZX09nTDB6bg?oc=5\", \"content\": \"Joe Rogan’s new custom Tesla Model S Plaid looks sick - Electrek.co\"}, {\"url\": \"https://news.google.com/rss/articles/CBMilAFBVV95cUxPbFR3LUQyX25rNUJIWjgwQTd1b2hIVmNudUYzMnYwVlFKSmZ1MnBPTjZiSDJTQ29MbTd6TGNDdVc5dm9JMjJXV0FpazN1OXhOa0pxNkxIQ3ZWVFV4cmdiMlo2b3VqeXF1SXhQMnJ3aFlhM1BMdFBEOUplZ0RrNU5kR09uQlF4cGZ4SWdicWd3aEdrazdS?oc=5\", \"content\": \"2025 Tesla Model Y Juniper vs. 2025 Tesla Model 3: New EVs Compared - Car and Driver\"}, {\"url\": \"https://news.google.com/rss/articles/CBMigwFBVV95cUxNZFpPT05uWFdYLXUzSXh3bjRzbk1aMkpKZ0tqMGY2UF9iZWNVdUYzaHIxZzZFTkJVLWpiY2NtZll4UldIdUZaSnRHSWdKUnc3VDNQQmMyX2ZJNFl4cExmdFFrS1hBQ3lKaVIxOFQ3SVZUSEVxRzJSSkEyMDRtb1NnWWZKOA?oc=5\", \"content\": \"New Tesla Model Y First Look: The Model 3\\'s Upgrades Hit the More Popular SUV - MotorTrend\"}, {\"url\": \"https://news.google.com/rss/articles/CBMivwFBVV95cUxPb2xCdF9qRGp6TmdrMUVtZnJ1SUJXWC1lajdPMU5JTF9PcnlSTDktbTU1eUVDVkxjNnhYMDdSeFVZcWVmMVQ0LXVUWTRhNzlHa0ZBMmJfZUpGQm9mcHNzcEZ1Tk1PQVdlVnh6RUpVc1NRQUZTNlN3MnlodEhNSThydm1YU0JrV2h2T0R5dXJWdHF4TkpfLTBTOGNnN1RFLVdBNVhqd0V0TExGRmFOdVR1VFVmTi1CUXlmQW5DbDA3aw?oc=5\", \"content\": \"Tesla launches new Model Y in US, Canada, Europe weeks after China - Reuters\"}]', name='search_news', tool_call_id='tool_call_id')]}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.messages import AIMessage\n",
    "\n",
    "# Creating an AI message object with single tool call\n",
    "message_with_single_tool_call = AIMessage(\n",
    "    content=\"\",\n",
    "    tool_calls=[\n",
    "        {\n",
    "            \"name\": \"search_news\",\n",
    "            \"args\": {\"query\": \"Tesla new model\"},\n",
    "            \"id\": \"tool_call_id\",\n",
    "            \"type\": \"tool_call\",\n",
    "        }\n",
    "    ],\n",
    ")\n",
    "\n",
    "# Invoke single tool call with created message\n",
    "tool_node.invoke({\"messages\": [message_with_single_tool_call]})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "949b65e4",
   "metadata": {},
   "source": [
    "Generally, there is no need to manually create an ```AIMessage```, as it is automatically generated by all LangChain chat models that support tool invocation.\n",
    "\n",
    "Additionally, by passing multiple tool invocations to the ```tool_calls``` parameter of an ```AIMessage```, you can perform parallel tool invocations using ```ToolNode```."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "9ea774db",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [ToolMessage(content='[{\"url\": \"https://news.google.com/rss/articles/CBMingFBVV95cUxNSjY3UDQyQ0xaSlNUcDBQV0I3VnBJck9wSkRWdTJ4ZU0yUkQwQm9iVGNaYjRva3ZqTmR3ZHFySU9VQTM5OW04NTNCZm8yZU0tekFYSEhLSmFiNGQ0VlNna1VhQk1aS3Y0ZUc5QllXdjA1Z3VCTVBFUW9iUEJhMHBsWWthWGdiTUw4RnZlNENETXNWXzQ0MGFLUXJYMXpfZ9IBowFBVV95cUxNQXVlRG50cGJxUmZNSkFSbnBua1hkaFhZNi14V25PWkxJRDZPZkhxRDVYV3NnQm1xaWZFMEFMeHhESGtwWFBlOUpueGlTa0NKUlNDV2R3N3VfTi1wWmFNdVhGNE85czd5aU9oOGpmc2xkSWM1Q1JZWXgycXZ1X3lPd0ROTHVEOEVOSWxfMzlLTnltaG1ESjdhMWZvZFYyWC10Y1lR?oc=5\", \"content\": \"Tesla starts sales of revamped Model Y in U.S. for about $60,000 - CNBC\"}, {\"url\": \"https://news.google.com/rss/articles/CBMijAFBVV95cUxQUDhkX2tIUmRNT1J2VExLYmpsYzNSMlB0cTFIcmg5aUtiVEE2cnhfb1lWOGRCTVR4bjYyMk1TMk0tUDc4d3Y5Y1BKd2o0TjZaWnlEa3JKbjY0djh3M25DX0U5dlFGM0RUcFVidllYaDdFcUFGNjBuYkdtWGM4VlRGQi05TElZX09nTDB6bg?oc=5\", \"content\": \"Joe Rogan’s new custom Tesla Model S Plaid looks sick - Electrek.co\"}, {\"url\": \"https://news.google.com/rss/articles/CBMilAFBVV95cUxPbFR3LUQyX25rNUJIWjgwQTd1b2hIVmNudUYzMnYwVlFKSmZ1MnBPTjZiSDJTQ29MbTd6TGNDdVc5dm9JMjJXV0FpazN1OXhOa0pxNkxIQ3ZWVFV4cmdiMlo2b3VqeXF1SXhQMnJ3aFlhM1BMdFBEOUplZ0RrNU5kR09uQlF4cGZ4SWdicWd3aEdrazdS?oc=5\", \"content\": \"2025 Tesla Model Y Juniper vs. 2025 Tesla Model 3: New EVs Compared - Car and Driver\"}, {\"url\": \"https://news.google.com/rss/articles/CBMigwFBVV95cUxNZFpPT05uWFdYLXUzSXh3bjRzbk1aMkpKZ0tqMGY2UF9iZWNVdUYzaHIxZzZFTkJVLWpiY2NtZll4UldIdUZaSnRHSWdKUnc3VDNQQmMyX2ZJNFl4cExmdFFrS1hBQ3lKaVIxOFQ3SVZUSEVxRzJSSkEyMDRtb1NnWWZKOA?oc=5\", \"content\": \"New Tesla Model Y First Look: The Model 3\\'s Upgrades Hit the More Popular SUV - MotorTrend\"}, {\"url\": \"https://news.google.com/rss/articles/CBMivwFBVV95cUxPb2xCdF9qRGp6TmdrMUVtZnJ1SUJXWC1lajdPMU5JTF9PcnlSTDktbTU1eUVDVkxjNnhYMDdSeFVZcWVmMVQ0LXVUWTRhNzlHa0ZBMmJfZUpGQm9mcHNzcEZ1Tk1PQVdlVnh6RUpVc1NRQUZTNlN3MnlodEhNSThydm1YU0JrV2h2T0R5dXJWdHF4TkpfLTBTOGNnN1RFLVdBNVhqd0V0TExGRmFOdVR1VFVmTi1CUXlmQW5DbDA3aw?oc=5\", \"content\": \"Tesla launches new Model Y in US, Canada, Europe weeks after China - Reuters\"}]', name='search_news', tool_call_id='tool_call_id_1'),\n",
       "  ToolMessage(content='10\\n', name='python_code_interpreter', tool_call_id='tool_call_id_2')]}"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Creating an AI message object with multiple tool calls\n",
    "message_with_multiple_tool_calls = AIMessage(\n",
    "    content=\"\",\n",
    "    tool_calls=[\n",
    "        {\n",
    "            \"name\": \"search_news\",\n",
    "            \"args\": {\"query\": \"Tesla new model\"},\n",
    "            \"id\": \"tool_call_id_1\",\n",
    "            \"type\": \"tool_call\",\n",
    "        },\n",
    "        {\n",
    "            \"name\": \"python_code_interpreter\",\n",
    "            \"args\": {\"code\": \"print(1+2+3+4)\"},\n",
    "            \"id\": \"tool_call_id_2\",\n",
    "            \"type\": \"tool_call\",\n",
    "        },\n",
    "    ],\n",
    ")\n",
    "\n",
    "# Invoke multiple tool calls with created message\n",
    "tool_node.invoke({\"messages\": [message_with_multiple_tool_calls]})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d079b5b9",
   "metadata": {},
   "source": [
    "## Using with LLMs\n",
    "\n",
    " To use chat models with tool calling, we need to first ensure that the model is aware of the available tools. \n",
    " LangChain provides various chat models of different providers such as ```OpenAI GPT```, ```Anthropic Claude```, ```Google Gemini``` and more.\n",
    " You can visit [LangChain ChatModels](https://python.langchain.com/docs/integrations/chat/) for more details.\n",
    " \n",
    "In this tutorial, We do this by calling ```.bind_tools``` method on ```ChatOpenAI``` model.\n",
    "This can be done by calling the ```.bind_tools``` method on the ```ChatOpenAI``` model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "af53996f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# Initialize LLM model and bind tools\n",
    "model_with_tools = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0).bind_tools(tools)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "9e4d2811",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'name': 'python_code_interpreter',\n",
       "  'args': {'code': 'def is_prime(n):\\n    if n <= 1:\\n        return False\\n    for i in range(2, int(n**0.5) + 1):\\n        if n % i == 0:\\n            return False\\n    return True\\n\\nprimes = []\\nnum = 2\\nwhile len(primes) < 5:\\n    if is_prime(num):\\n        primes.append(num)\\n    num += 1\\n\\nprint(primes)'},\n",
       "  'id': 'call_izRKAOviyEZpHGYmTPqOaz0z',\n",
       "  'type': 'tool_call'}]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model_with_tools.invoke(\n",
    "    \"Write Python code to print the first 5 prime numbers.\"\n",
    ").tool_calls"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d13a07e4",
   "metadata": {},
   "source": [
    "As you can see, the AI message generated by the chat model already has ```tool_calls``` populated, so we can just pass it directly to ```ToolNode```."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "8c5dc7d5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [ToolMessage(content='[2, 3, 5, 7, 11]\\n', name='python_code_interpreter', tool_call_id='call_3NCmJT0R6JPKulLwG0qPqp9T')]}"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Processing messages through ToolNode and generating tool-based responses from LLM model\n",
    "tool_node.invoke(\n",
    "    {\n",
    "        \"messages\": [\n",
    "            model_with_tools.invoke(\n",
    "                \"Write Python code to print the first 5 prime numbers.\"\n",
    "            )\n",
    "        ]\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e1fb0371",
   "metadata": {},
   "source": [
    "## Using with Agent\n",
    "\n",
    "Next, let's explore how to use ```ToolNode``` within a ```LangGraph```' graph.\n",
    "\n",
    "We will set up an [```ReAct Agent```](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/#react-implementation)'s graph implementation. This agent takes a query as input and repeatedly invokes tools until it gathers enough information to resolve the query.\n",
    "\n",
    "Before we start, let's define a function to visualize the graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "ccd4e3ba",
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import Image, display\n",
    "from langgraph.graph.state import CompiledStateGraph\n",
    "\n",
    " # Graph visualization\n",
    "def visualize_graph(graph, xray=False):\n",
    "    \"\"\"\n",
    "    Visualizes and displays a CompiledStateGraph object.\n",
    "    \n",
    "    This function converts and displays the given graph object as a PNG image in Mermaid format if it is an instance of CompiledStateGraph.\n",
    "\n",
    "    Args:\n",
    "        graph: The graph object to visualize. It must be an instance of CompiledStateGraph.\n",
    "\n",
    "    Returns:\n",
    "        None\n",
    "\n",
    "    Raises:\n",
    "        Exception: Outputs an exception if an error occurs during graph visualization.\n",
    "    \"\"\"\n",
    "    try:\n",
    "        if isinstance(graph, CompiledStateGraph):\n",
    "            display(\n",
    "                Image(\n",
    "                    graph.get_graph(xray=xray).draw_mermaid_png(\n",
    "                        background_color=\"white\",\n",
    "                        node_colors=None,\n",
    "                    )\n",
    "                )\n",
    "            )\n",
    "    except Exception as e:\n",
    "        print(f\"[ERROR] Visualize Graph Error: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c6d4b13",
   "metadata": {},
   "source": [
    "The ```ToolNode``` and ```OpenAI``` model will be used together with the tools we just defined.\n",
    "\n",
    "Let's build a graph with the following steps.\n",
    "1. Use LLM model to process messages and generate responses, return responses with tool calls.\n",
    "2. Initialize workflow graph based on message state.\n",
    "3. Define the two nodes we will cycle between agent and tools.\n",
    "4. Connect the workflow starting point to the agent node.\n",
    "5. Set up conditional branching from the agent node, connecting to a tool node or an endpoint.\n",
    "6. Set up circular edges between the tool node and the agent node.\n",
    "7. Connect the agent node to the end point.\n",
    "8. Compile the defined workflow graph and create an executable application."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "1920a041",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import types for LangGraph workflow state and message processing\n",
    "from langgraph.graph import StateGraph, MessagesState, START, END\n",
    "\n",
    "\n",
    "# 1. Use LLM model to process messages and generate responses, return responses with tool calls\n",
    "def call_model(state: MessagesState):\n",
    "    messages = state[\"messages\"]\n",
    "    response = model_with_tools.invoke(messages)\n",
    "    return {\"messages\": [response]}\n",
    "\n",
    "\n",
    "# 2. Initialize workflow graph based on message state\n",
    "workflow = StateGraph(MessagesState)\n",
    "\n",
    "# 3. Define the two nodes we will cycle between agent and tools\n",
    "# Add agent and tools nodes to the workflow graph\n",
    "workflow.add_node(\"agent\", call_model)\n",
    "workflow.add_node(\"tools\", tool_node)\n",
    "\n",
    "# 4. Connect the workflow starting point to the agent node\n",
    "workflow.add_edge(START, \"agent\")\n",
    "\n",
    "# 5.Set up conditional branching from the agent node, connecting to a tool node or an endpoint\n",
    "workflow.add_conditional_edges(\"agent\", tools_condition)\n",
    "\n",
    "# 6. Set up circular edges between the tool node and the agent node\n",
    "workflow.add_edge(\"tools\", \"agent\")\n",
    "\n",
    "# 7. Connect the agent node to the end point\n",
    "workflow.add_edge(\"agent\", END)\n",
    "\n",
    "# 8. Compile the defined workflow graph and create an executable application\n",
    "app = workflow.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "7e7b9e45",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "visualize_graph(app)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e14778c",
   "metadata": {},
   "source": [
    "Let's try to run the graph with different queries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "1d8e843e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Write Python code to print the first 5 prime numbers.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  python_code_interpreter (call_xYrazUEJHCaoBRGPYm3wZvyU)\n",
      " Call ID: call_xYrazUEJHCaoBRGPYm3wZvyU\n",
      "  Args:\n",
      "    code: def is_prime(n):\n",
      "    if n <= 1:\n",
      "        return False\n",
      "    for i in range(2, int(n**0.5) + 1):\n",
      "        if n % i == 0:\n",
      "            return False\n",
      "    return True\n",
      "\n",
      "primes = []\n",
      "num = 2\n",
      "while len(primes) < 5:\n",
      "    if is_prime(num):\n",
      "        primes.append(num)\n",
      "    num += 1\n",
      "\n",
      "print(primes)\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: python_code_interpreter\n",
      "\n",
      "[2, 3, 5, 7, 11]\n",
      "\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "The first 5 prime numbers are: [2, 3, 5, 7, 11].\n"
     ]
    }
   ],
   "source": [
    "for chunk in app.stream(\n",
    "    {\"messages\": [(\"human\", \"Write Python code to print the first 5 prime numbers.\")]},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    # Print the last message\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "71ce609d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "What is most played BTS song?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "The most played BTS song is \"Dynamite.\" Released in August 2020, it became a global hit and topped charts worldwide, including the Billboard Hot 100. The song's catchy melody and upbeat vibe contributed to its massive popularity, leading to billions of streams on various platforms. Other popular BTS songs include \"Butter,\" \"Boy With Luv,\" and \"Fake Love,\" but \"Dynamite\" remains one of their most iconic tracks.\n"
     ]
    }
   ],
   "source": [
    "# Search query\n",
    "for chunk in app.stream(\n",
    "    {\"messages\": [(\"human\", \"What is most played BTS song?\")]},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "96c5c75d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Hi, How's it going?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "I'm just a program, so I don't have feelings, but I'm here and ready to help you! How can I assist you today?\n"
     ]
    }
   ],
   "source": [
    "# Query that does not require tool call\n",
    "for chunk in app.stream(\n",
    "    {\"messages\": [(\"human\", \"Hi, How's it going?\")]},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d39c30b4",
   "metadata": {},
   "source": [
    "```ToolNode``` can also handle errors that occur during tool execution.\n",
    "\n",
    "You can enable/disable this feature by setting ```handle_tool_errors=True``` (enabled by default)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-opentutorial-nHTobcyW-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
